C#: pass single-field exposed structs through DllImport as their scalar (IL2CPP wasm ABI) - #48
Closed
Grantim wants to merge 2 commits into
Closed
C#: pass single-field exposed structs through DllImport as their scalar (IL2CPP wasm ABI)#48Grantim wants to merge 2 commits into
Grantim wants to merge 2 commits into
Conversation
Unity's IL2CPP wraps every C# struct into a union with padding, and on wasm32 Clang only passes/returns single-element structs as plain scalars, so the IL2CPP call site used a hidden return pointer and pass-by-pointer while the C library used a plain `int` (`wasm-ld: function signature mismatch`, traps or garbage at runtime). Declaring the `DllImport` with the field's scalar type has the same ABI on both sides; the C API and the public C# API are unchanged. Structs with several fields already agreed on both sides and are left as is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds `test_exposed_c()` taking and returning a single-field exposed struct by value, and regenerates the C and C# test outputs. The C# diff shows the new `DllImport` shape: `int` in place of the struct, `.x` at the call sites, and `new T {x = ...}` on return, including constructors and the `std::function` callback wrapper.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
Author
|
Superseded by #49, which fixes the same IL2CPP/wasm ABI mismatch with a much smaller change (LayoutKind.Sequential for single-field exposed structs instead of rewriting the DllImport signatures). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Unity WebGL (IL2CPP) builds of MeshLib's wasm bindings link with 622
wasm-ld: warning: function signature mismatch, one for every C API function returning anMR::*Id:Those calls trap at runtime. Worse, the ~1000 C API functions that take an Id by value get no warning at all (same
i32on both sides), but IL2CPP passes a pointer to a stack copy where the C function reads the id.Root cause: IL2CPP emits every C# struct as
struct { union { struct { ... }; uint8_t padding[N]; }; }, and Clang's wasm32 ABI only flattens single-element structs. So a one-field exposed struct becomes an aggregate on the IL2CPP side (sret return, pass by pointer), while the C side returns/accepts a plainint. Multi-field structs (Vector3f,Box3f, ...) are unaffected: both sides already pass them indirectly. Reproduced with Unity's own emcc, and the 622 warned functions are exactly the set of P/Invokes returning an Id type.Fix
If an exposed struct has exactly one non-static field, and that field is an arithmetic type other than
boolor an enum, the C# generator now declares theDllImportparameters/return as that scalar and converts at the boundary (v.xon the way in,new T {x = ...}on the way out). A scalar has the same ABI on every runtime and target. The public C# API and the C API are unchanged (in C both spellings have the same ABI on all supported platforms). Pointer paths (this,in, defaulted params) are untouched.The two constructor emission paths (
this = ...and the Box wrapper's*(T *)_UnderlyingPtr = ...) now go through the return binding so they compose with this; for other types they produce the same text as before.Verification
llvm-22.1.8libs, like CI). The diff is confined to single-field exposed structs (ConvCtorExposed,ExposedLayoutB,ExposedLayoutC,DeclOrder::*), including constructors and thestd::function<ExposedLayoutC(ExposedLayoutC)>callback wrapper.dotnet buildof the three generated C# test projects: 0 warnings.test_exposed_c()to cover a by-value parameter and return value in a plain function.Follow-up in MeshLib: bump mrbind and regenerate the C# bindings; the Unity wasm link should then be free of
function signature mismatchwarnings.🤖 Generated with Claude Code